Feature/solution skvs#766
Conversation
05c5cd2 to
8fb8b84
Compare
| // chaincode := fpc.NewPrivateChaincode(secretChaincode) | ||
| skvsChaincode := fpc.NewSkvsChaincode(secretChaincode) | ||
|
|
||
| // start chaincode as a service | ||
| server := &shim.ChaincodeServer{ | ||
| CCID: ccid, | ||
| Address: addr, | ||
| CC: chaincode, | ||
| CC: skvsChaincode, |
There was a problem hiding this comment.
I was wondering if we want multiple main files? So we have an example to run secret keeper with and without skvs?
/samples/chaincode/secret-keeper-go/cmd/simple/main.go
/samples/chaincode/secret-keeper-go/cmd/skvs/main.go
A few words in the secret-keeper readme would be nice as well.
| "github.com/pkg/errors" | ||
| ) | ||
|
|
||
| type SkvsStubInterface struct { |
There was a problem hiding this comment.
the name of this file should be all small caps
| return &skvsStub{enclaveStub} | ||
| } | ||
|
|
||
| func (e *skvsStub) ChaincodeInvoke(stub shim.ChaincodeStubInterface, chaincodeRequestMessageBytes []byte) ([]byte, error) { |
There was a problem hiding this comment.
I have the feeling that we should remove this wrapper and instead just inject a provider function for the stub that we can set externally.
There was a problem hiding this comment.
no sure about this one, might need to ask more insight
dbe4851 to
754f02d
Compare
|
@chenchanglew can you please rebase in resolve the conflict within |
|
@munapower can you also please have look at this PR. Thanks! |
@mbrandenburger @chenchanglew not sure what I am supposed to test. @chenchanglew did you resolve the conflict Marcus mentions? What am I supposed to test? Should I follow the Readme or what? |
| DEFAULT= cmd/naive/main.go | ||
| SKVS_PATH = cmd/skvs/main.go | ||
|
|
||
| MAIN_GO_PATH ?=$(DEFAULT) | ||
|
|
||
| include $(TOP)/ecc_go/build.mk |
There was a problem hiding this comment.
We should add a small section in the readme of this demo to explain how to build the chaincode with these different options.
For example:
MAIN_GO_PATH=cmd/naive/main.go make
or
MAIN_GO_PATH=cmd/skvs/main.go make
There was a problem hiding this comment.
There is nothing else the user need to change in order to use skvs, right?
There was a problem hiding this comment.
this is just build for different solutions,
the real change need to do is here
secretChaincode, _ := contractapi.NewChaincode(&chaincode.SecretKeeper{})
// chaincode := fpc.NewPrivateChaincode(secretChaincode)
skvsChaincode := fpc.NewSkvsChaincode(secretChaincode)
| func NewSkvsChaincode(cc shim.Chaincode) *chaincode.EnclaveChaincode { | ||
| logger.Info("Creating new SKVS Chaincode") | ||
| ecc := &chaincode.EnclaveChaincode{ | ||
| Enclave: enclave_go.NewSkvsStub(cc), | ||
| Validator: endorsement.NewValidator(), | ||
| Extractor: &chaincode.ExtractorImpl{}, | ||
| Ercc: &ercc.StubImpl{}, | ||
| } | ||
| return ecc | ||
| } |
There was a problem hiding this comment.
I am wondering if it would be better to add an option to the NewPrivateChaincode method to instantiate the with SKVS rather than creating this new constructor.
It could look like ...
func NewPrivateChaincode(cc shim.Chaincode, options ...func(*chaincode.EnclaveChaincode)) *chaincode.EnclaveChaincode {
ecc := &chaincode.EnclaveChaincode{
Enclave: enclave_go.NewSkvsStub(cc),
Validator: endorsement.NewValidator(),
Extractor: &chaincode.ExtractorImpl{},
Ercc: &ercc.StubImpl{},
}
for _, o := range options {
o(ecc)
}
return ecc
}
func WithSKVS() func(*chaincode.EnclaveChaincode) {
return func(ecc *chaincode.EnclaveChaincode) {
ecc.Enclave = enclave_go.NewSkvsStub(cc)
}
}and in the chaincode main.go, the developer would write something like that ...
// naive
chaincode := fpc.NewPrivateChaincode(secretChaincode)
// with SKVS
chaincode := fpc.NewPrivateChaincode(secretChaincode, fpc.WithSKVS)See this article https://golang.cafe/blog/golang-functional-options-pattern.html
WDYT?
754f02d to
dca2e20
Compare
mbrandenburger
left a comment
There was a problem hiding this comment.
Thanks Zac for the refactoring. I've found a few things we should still fix. Please have a look.
| func NewPrivateChaincode(cc shim.Chaincode, options ...BuildOption) *chaincode.EnclaveChaincode { | ||
| ecc := &chaincode.EnclaveChaincode{ | ||
| Enclave: enclave_go.NewEnclaveStub(cc), | ||
| Enclave: enclave_go.NewSkvsStub(cc), |
There was a problem hiding this comment.
I believe here we want to keep the "normal" NewEnclaveStub
| } | ||
| } | ||
|
|
||
| logger.Warningf("SKVS Init finish, allDataOld: %s, allDataNew: %s", s.allDataOld, s.allDataNew) |
There was a problem hiding this comment.
I would suggest that we remove these loggers as they may print sensitive data.
| ) | ||
|
|
||
| func NewSkvsStub(cc shim.Chaincode) *EnclaveStub { | ||
| logger.Warning("==== SKVS NewSkvsStub ====") |
There was a problem hiding this comment.
| logger.Warning("==== SKVS NewSkvsStub ====") |
| } | ||
|
|
||
| func NewSkvsStubInterface(stub shim.ChaincodeStubInterface, input *pb.ChaincodeInput, rwset *readWriteSet, sep StateEncryptionFunctions) *SkvsStubInterface { | ||
| logger.Warning("==== Get New Skvs Interface =====") |
There was a problem hiding this comment.
| logger.Warning("==== Get New Skvs Interface =====") |
| skvsStub := SkvsStubInterface{fpcStub, map[string][]byte{}, map[string][]byte{}, "SKVS"} | ||
| err := skvsStub.InitSKVS() | ||
| if err != nil { | ||
| logger.Warningf("Error!! Initializing SKVS failed") |
There was a problem hiding this comment.
If we have an error ... shouldn't we better through a panic? We cannot really go on and continue, right?
| skvsStub := SkvsStubInterface{fpcStub, map[string][]byte{}, map[string][]byte{}, "SKVS"} | ||
| err := skvsStub.InitSKVS() | ||
| if err != nil { | ||
| logger.Warningf("Error!! Initializing SKVS failed") |
There was a problem hiding this comment.
| logger.Warningf("Error!! Initializing SKVS failed") | |
| panic("Initializing SKVS failed") |
| } | ||
|
|
||
| func (s *SkvsStubInterface) InitSKVS() error { | ||
| logger.Warningf(" === Initializing SKVS === ") |
There was a problem hiding this comment.
| logger.Warningf(" === Initializing SKVS === ") |
| if len(encValue) == 0 { | ||
| logger.Warningf("SKVS is empty, Initiating.") |
There was a problem hiding this comment.
should we also handle this error differently? Can we actually continue if encValue is empty?
There was a problem hiding this comment.
yes, we can continue if the encValue is empty.
| func (s *SkvsStubInterface) InitSKVS() error { | ||
| logger.Warningf(" === Initializing SKVS === ") | ||
|
|
||
| // get current state, this will only operate once |
There was a problem hiding this comment.
Do you think it makes sense to protect the entire Init method with sync.Once?
https://pkg.go.dev/sync#Once
There was a problem hiding this comment.
I think this function is safe even it runs multiple time, since the
encValue, err := s.GetPublicState(s.key) will always fetch the latest value, if someone try to do something malicious by calling this init function they will just get the latest public state.
Plus I already change the init function into a private function thus I think it should be okay without the sync.Once.
| if err != nil { | ||
| return err | ||
| } | ||
| logger.Warningf("SKVS has default value, loading current value.") |
There was a problem hiding this comment.
| logger.Warningf("SKVS has default value, loading current value.") | |
| logger.Debug("SKVS has default value, loading current value.") |
f695c07 to
07e6fa9
Compare
| allDataOld: map[string][]byte{}, | ||
| allDataNew: map[string][]byte{}, |
There was a problem hiding this comment.
| allDataOld: map[string][]byte{}, | |
| allDataNew: map[string][]byte{}, | |
| allDataOld: make(map[string][]byte), | |
| allDataNew: make(map[string][]byte), |
| } | ||
| err := skvsStub.initSKVS() | ||
| if err != nil { | ||
| panic("Initializing SKVS failed") |
There was a problem hiding this comment.
| panic("Initializing SKVS failed") | |
| panic(fmt.Sprintf("Initializing SKVS failed, err: %v", err)) |
| func (s *SkvsStubInterface) GetState(key string) ([]byte, error) { | ||
| value, found := s.allDataOld[key] | ||
| if !found { | ||
| return nil, errors.New("skvs allDataOld key not found") |
There was a problem hiding this comment.
Mhh, shouldn't we return nil, nil in that case?
// If the key does not exist in the state database, (nil, nil) is returned.
GetState(key string) ([]byte, error)
https://github.com/hyperledger/fabric-chaincode-go/blob/main/shim/interfaces.go#L80C1-L81C38
|
|
||
| "github.com/hyperledger/fabric-chaincode-go/shim" | ||
| pb "github.com/hyperledger/fabric-protos-go/peer" | ||
| "github.com/pkg/errors" |
There was a problem hiding this comment.
| "github.com/pkg/errors" |
| func (s *SkvsStubInterface) GetState(key string) ([]byte, error) { | ||
| value, found := s.allDataOld[key] | ||
| if !found { | ||
| return nil, errors.New("skvs allDataOld key not found") |
There was a problem hiding this comment.
| return nil, errors.New("skvs allDataOld key not found") | |
| return nil, nil |
Signed-off-by: chenchanglew <lewchenchang@gmail.com>
Signed-off-by: chenchanglew <lewchenchang@gmail.com>
Signed-off-by: chenchanglew <lewchenchang@gmail.com>
Signed-off-by: chenchanglew <lewchenchang@gmail.com>
Signed-off-by: chenchanglew <lewchenchang@gmail.com>
Signed-off-by: chenchanglew <lewchenchang@gmail.com>
Signed-off-by: chenchanglew <lewchenchang@gmail.com>
Signed-off-by: chenchanglew <lewchenchang@gmail.com>
Signed-off-by: chenchanglew <lewchenchang@gmail.com>
Signed-off-by: chenchanglew <lewchenchang@gmail.com>
Signed-off-by: chenchanglew <lewchenchang@gmail.com>
Signed-off-by: chenchanglew <lewchenchang@gmail.com>
Signed-off-by: chenchanglew <lewchenchang@gmail.com>
07e6fa9 to
f6c2ef5
Compare
This adds single key-Value store abstraction layer to protected against rollback attacks. Signed-off-by: chenchanglew <lewchenchang@gmail.com> Signed-off-by: Abhinav Prakash <abhinav.prakash319@gmail.com>
What this PR does / why we need it:
Implement a Rollback attack protection solution for FPC: SKVS.
Single Key-Value Storage (SKVS) is a naive approach for rollback attacks. All key-value pairs are encapsulated and stored in this approach with a single call to put_state(). During execution, the enclave must load the entire state before accessing individual key-value pairs. While this approach prevents the rollback attack, applications with large states and multiple writers will experience bad performance, as the use of a single key-value pair will cause transactions to fail due to concurrent write issues.
A user can use it by changing the chain code to SVKS chaincode
ex:
skvsChaincode := fpc.NewPrivateChaincode(secretChaincode, fpc.WithSKVS())for example we can test with skvs/main.go in secret keeper
make ECC_MAIN_FILES=cmd/skvs/main.go with_go env dockerWhich issue(s) this PR fixes:
Fixes #484
Special notes for your reviewer:
Loom demonstration video: Watch here